{{!

  Copyright (c) Meta Platforms, Inc. and affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{!

Python wrappers of the structs defined in the services files. This file is
compiled into it's own module to be included by clients and services and
end-user code. It's one of the more complicated files, as it has to map
Pythonic APIs to C++ objects and back.

One of the nastier things in this file is the definition of containers.
A separate container wrapper has to be defined for each type of contained
attribute because Cython can't template C++ classes. So, for example, we need
a List__int16 or a List__string or a Map__string_mystruct instance for each
container/type combination. Consider that containers can contain other containers
or structs that contain containers and you realize how messy this can get.
Further, we'd prefer to have the end user freed from having to know about these
container types, so we'll need to define factories for them based on what they
want to include.

}}
{{#type:list?}}
class {{type:flat_name}}(thrift.py3.types.List):
    {{! make the type appear to originate from .types module }}
    __module__ = _fbthrift__module_name__
    {{! __slots__ prevents accidental treatment as py-deprecated }}
    __slots__ = ()

    def __init__(self, items=None, private_ctor_token=None) -> None:
        if private_ctor_token is thrift.py3.types._fbthrift_list_private_ctor:
            {{!
                For internal use only, when items list has been created internally
                and each item has been converted from cpp so we can trust the type.
                Do not use with user-exposed `list` to avoid accidental mutability.
            }}
            _py_obj = items
        elif isinstance(items, {{type:flat_name}}):
            _py_obj = list(items)
        elif items is None:
            _py_obj = []
        else:
            {{#type:containerOfString?}}
            if isinstance(items, str):
                raise TypeError("If you really want to pass a string into a {{> types/pep484_type}} field, explicitly convert it first.")
            {{/type:containerOfString?}}
            check_method = {{type:flat_name}}._check_item_type_or_raise
            _py_obj = [check_method(item) for item in items]

        super().__init__(_py_obj, {{type:flat_name}})

    @staticmethod
    def _check_item_type_or_raise(item):
    {{#type:list_elem_type}}
    {{#type:container?}}
        if item is None:
            raise TypeError("None is not of the type {{> types/pep484_type}}")
        if not isinstance(item, {{> types/python_type}}):
            item = {{> types/python_type}}(item)
    {{/type:container?}}
    {{^type:container?}}
        if not (
            isinstance(item, {{> types/python_is_instance_type}}){{#type:enum?}} or
            isinstance(item, thrift.py3.types.BadEnum){{/type:enum?}}
        ):
            raise TypeError(f"{item!r} is not of type {{> types/pep484_type}}")
    {{/type:container?}}
    {{/type:list_elem_type}}
        return item

    {{! the __contains__, index, and count APIs don't raise TypeError}}
    @staticmethod
    def _check_item_type_or_none(item):
        if item is None:
            return None
        {{#type:list_elem_type}}
        if isinstance(item, {{> types/python_type}}):
            return item
        {{#type:container?}}
        try:
            return {{> types/python_type}}(item)
        except:
            pass
        {{/type:container?}}
        {{/type:list_elem_type}}

    @staticmethod
    def __get_reflection__():
        return get_types_reflection().get_reflection__{{type:flat_name}}()

{{#program:inplace_migrate?}}
    @staticmethod
    def from_python(python_list: thrift.python.types.List) -> {{type:flat_name}}:
    {{#type:list_elem_type}}
    {{^type:needs_convert?}}
        _items = list(python_list)
    {{/type:needs_convert?}}
    {{#type:needs_convert?}}
        _items = [
            {{^type:float?}}
            {{> types/python_type}}.from_python(item)
            {{/type:float?}}{{#type:float?}}
            _fbthrift__round_float32(item)
            {{/type:float?}}
            for item in python_list
        ]
    {{/type:needs_convert?}}
    {{/type:list_elem_type}}
        return {{type:flat_name}}(
            items=_items,
            private_ctor_token=thrift.py3.types._fbthrift_list_private_ctor,
        )

{{/program:inplace_migrate?}}
{{^program:inplace_migrate?}}
{{#type:is_container_of_struct?}}
    @staticmethod
    def from_python(python_list: thrift.python.types.List) -> "{{type:flat_name}}":
    {{#type:list_elem_type}}
        _items = [
            {{#type:is_container_of_struct?}}
            {{> types/python_type}}.from_python(item)
            {{/type:is_container_of_struct?}}
            {{^type:is_container_of_struct?}}
            _ensure_py3_or_raise(item, "item", {{> types/python_is_instance_type}})
            {{/type:is_container_of_struct?}}
            for item in python_list
        ]
    {{/type:list_elem_type}}
        return {{type:flat_name}}(
            items=_items,
            private_ctor_token=thrift.py3.types._fbthrift_list_private_ctor,
        )
{{/type:is_container_of_struct?}}
{{/program:inplace_migrate?}}

Sequence.register({{type:flat_name}})

{{/type:list?}}{{#type:set?}}
class {{type:flat_name}}(thrift.py3.types.Set):
    {{! make the type appear to originate from .types module }}
    __module__ = _fbthrift__module_name__
    {{! __slots__ prevents accidental treatment as py-deprecated }}
    __slots__ = ()

    def __init__(self, items=None, private_ctor_token=None) -> None:
        if private_ctor_token is thrift.py3.types._fbthrift_set_private_ctor:
            {{!
                For internal use only, when items set has been created internally
                and each item has been converted from cpp so we can trust the type.
                Do not use with user-exposed `set` to avoid accidental mutability.
            }}
            _py_obj = items
        elif isinstance(items, {{type:flat_name}}):
            _py_obj = frozenset(items)
        elif items is None:
            _py_obj = frozenset()
        else:
            {{#type:containerOfString?}}
            if isinstance(items, str):
                raise TypeError("If you really want to pass a string into a {{> types/pep484_type}} field, explicitly convert it first.")
            {{/type:containerOfString?}}
            check_method = {{type:flat_name}}._check_item_type_or_raise
            _py_obj = frozenset(check_method(item) for item in items)

        super().__init__(_py_obj, {{type:flat_name}})

    @staticmethod
    def _check_item_type_or_raise(item):
    {{#type:set_elem_type}}
    {{#type:container?}}
        if item is None:
            raise TypeError("None is not of the type {{> types/pep484_type}}")
        if not isinstance(item, {{> types/python_type}}):
            item = {{> types/python_type}}(item)
    {{/type:container?}}
    {{^type:container?}}
        if not (
            isinstance(item, {{> types/python_is_instance_type}}){{#type:enum?}} or
            isinstance(item, thrift.py3.types.BadEnum){{/type:enum?}}
        ):
            raise TypeError(f"{item!r} is not of type {{> types/pep484_type}}")
    {{/type:container?}}
    {{/type:set_elem_type}}
        return item

    {{! __contains__ doesn't raise TypeError}}
    @staticmethod
    def _check_item_type_or_none(item):
        if item is None:
            return None
        {{#type:set_elem_type}}
        if isinstance(item, {{> types/python_type}}):
            return item
        {{#type:container?}}
        try:
            return {{> types/python_type}}(item)
        except:
            return None
        {{/type:container?}}
        {{/type:set_elem_type}}

    @staticmethod
    def __get_reflection__():
        return get_types_reflection().get_reflection__{{type:flat_name}}()

{{#program:inplace_migrate?}}
    @staticmethod
    def from_python(python_set: thrift.python.types.Set) -> {{type:flat_name}}:
    {{#type:set_elem_type}}
    {{^type:needs_convert?}}
        _items = frozenset(python_set)
    {{/type:needs_convert?}}
    {{#type:needs_convert?}}
        _items = frozenset(
            {{^type:float?}}
            {{> types/python_type}}.from_python(item)
            {{/type:float?}}{{#type:float?}}
            _fbthrift__round_float32(item)
            {{/type:float?}}
            for item in python_set
        )
    {{/type:needs_convert?}}
    {{/type:set_elem_type}}
        return {{type:flat_name}}(
            items=_items,
            private_ctor_token=thrift.py3.types._fbthrift_set_private_ctor,
        )

{{/program:inplace_migrate?}}
{{^program:inplace_migrate?}}
{{#type:is_container_of_struct?}}
    @staticmethod
    def from_python(python_set: thrift.python.types.Set) -> "{{type:flat_name}}":
    {{#type:set_elem_type}}
        _items = frozenset(
            {{#type:is_container_of_struct?}}
            {{> types/python_type}}.from_python(item)
            {{/type:is_container_of_struct?}}
            {{^type:is_container_of_struct?}}
            _ensure_py3_or_raise(item, "item", {{> types/python_is_instance_type}})
            {{/type:is_container_of_struct?}}
            for item in python_set
        )
    {{/type:set_elem_type}}
        return {{type:flat_name}}(
            items=_items,
            private_ctor_token=thrift.py3.types._fbthrift_set_private_ctor,
        )
{{/type:is_container_of_struct?}}
{{/program:inplace_migrate?}}

Set.register({{type:flat_name}})

{{/type:set?}}{{#type:map?}}
class {{type:flat_name}}(thrift.py3.types.Map):
    {{! make the type appear to originate from .types module }}
    __module__ = _fbthrift__module_name__
    {{! __slots__ prevents accidental treatment as py-deprecated }}
    __slots__ = ()

    _FBTHRIFT_USE_SORTED_REPR = {{!
        }}{{#type:defaultTemplate?}}True{{/type:defaultTemplate?}}{{!
        }}{{^type:defaultTemplate?}}False{{/type:defaultTemplate?}}

    def __init__(self, items=None, private_ctor_token=None) -> None:
        if private_ctor_token is thrift.py3.types._fbthrift_map_private_ctor:
            {{!
                For internal use only, when items dict has been created internally
                and each item has been converted from cpp so we can trust the type.
                Do not use with user-exposed `dict` to avoid accidental mutability.
            }}
            _py_obj = items
        elif isinstance(items, {{type:flat_name}}):
            _py_obj = dict(items)
        elif items is None:
            _py_obj = dict()
        else:
            check_key = {{type:flat_name}}._check_key_type_or_raise
            check_val = {{type:flat_name}}._check_val_type_or_raise
            _py_obj = {check_key(k) : check_val(v) for k, v in items.items()}

        super().__init__(_py_obj, {{type:flat_name}})

    @staticmethod
    def _check_key_type_or_raise(key):
    {{#type:key_type}}
    {{#type:container?}}
        if key is None:
            raise TypeError("None is not of the type {{> types/pep484_type}}")
        if not isinstance(key, {{> types/python_type}}):
            key = {{> types/python_type}}(key)
    {{/type:container?}}
    {{^type:container?}}
        if not (
            isinstance(key, {{> types/python_is_instance_type}}){{#type:enum?}} or
            isinstance(key, thrift.py3.types.BadEnum){{/type:enum?}}
        ):
            raise TypeError(f"{key!r} is not of type {{> types/pep484_type}}")
    {{/type:container?}}
    {{/type:key_type}}
        return key

    {{! __contains__ doesn't raise TypeError; __getitem__ uses KeyError }}
    @staticmethod
    def _check_key_type_or_none(key):
        if key is None:
            return None
        {{#type:key_type}}
        if isinstance(key, {{> types/python_type}}):
            return key
        {{#type:container?}}
        try:
            return {{> types/python_type}}(key)
        except:
            return None
        {{/type:container?}}
        {{/type:key_type}}

    @staticmethod
    def _check_val_type_or_raise(item):
    {{#type:value_type}}
    {{#type:container?}}
        if item is None:
            raise TypeError("None is not of the type {{> types/pep484_type}}")
        if not isinstance(item, {{> types/python_type}}):
            item = {{> types/python_type}}(item)
    {{/type:container?}}
    {{^type:container?}}
        if not (
            isinstance(item, {{> types/python_is_instance_type}}){{#type:enum?}} or
            isinstance(item, thrift.py3.types.BadEnum){{/type:enum?}}
        ):
            raise TypeError(f"{item!r} is not of type {{> types/pep484_type}}")
    {{/type:container?}}
    {{/type:value_type}}
        return item

    @staticmethod
    def __get_reflection__():
        return get_types_reflection().get_reflection__{{type:flat_name}}()

{{#program:inplace_migrate?}}
    @staticmethod
    def from_python(python_map: thrift.python.types.Map) -> {{type:flat_name}}:
    {{#type:key_type}}
    {{^type:needs_convert?}}
        _keys = python_map.keys()
    {{/type:needs_convert?}}
    {{#type:needs_convert?}}
        _keys = (
            {{^type:float?}}
            {{> types/python_type}}.from_python(key)
            {{/type:float?}}{{#type:float?}}
            _fbthrift__round_float32(key)
            {{/type:float?}}
            for key in python_map.keys()
        )
    {{/type:needs_convert?}}
    {{/type:key_type}}
    {{#type:value_type}}
    {{^type:needs_convert?}}
        _values = python_map.values()
    {{/type:needs_convert?}}
    {{#type:needs_convert?}}
        _values = (
            {{^type:float?}}
            {{> types/python_type}}.from_python(item)
            {{/type:float?}}{{#type:float?}}
            _fbthrift__round_float32(item)
            {{/type:float?}}
            for item in python_map.values()
        )
    {{/type:needs_convert?}}
    {{/type:value_type}}
        return {{type:flat_name}}(
            items=dict(zip(_keys, _values)),
            private_ctor_token=thrift.py3.types._fbthrift_map_private_ctor,
        )

{{/program:inplace_migrate?}}
{{^program:inplace_migrate?}}
{{#type:is_container_of_struct?}}
    @staticmethod
    def from_python(python_map: thrift.python.types.Map) -> "{{type:flat_name}}":
    {{#type:key_type}}
    {{#type:is_container_of_struct?}}
        _keys = (
            {{> types/python_type}}.from_python(key)
            for key in python_map.keys()
        )
    {{/type:is_container_of_struct?}}
    {{^type:is_container_of_struct?}}
    {{#type:structured?}}
        _keys = (
            _ensure_py3_or_raise(key, "key", {{> types/python_is_instance_type}})
            for key in python_map.keys()
        )
    {{/type:structured?}}
    {{^type:structured?}}
        _keys = python_map.keys()
    {{/type:structured?}}
    {{/type:is_container_of_struct?}}
    {{/type:key_type}}
    {{#type:value_type}}
    {{#type:is_container_of_struct?}}
        _values = (
            {{> types/python_type}}.from_python(value)
            for value in python_map.values()
        )
    {{/type:is_container_of_struct?}}
    {{^type:is_container_of_struct?}}
    {{#type:structured?}}
        _values = (
            _ensure_py3_or_raise(value, "value", {{> types/python_is_instance_type}})
            for value in python_map.values()
        )
    {{/type:structured?}}
    {{^type:structured?}}
        _values = python_map.values()
    {{/type:structured?}}
    {{/type:is_container_of_struct?}}
    {{/type:value_type}}
        return {{type:flat_name}}(
            items=dict(zip(_keys, _values)),
            private_ctor_token=thrift.py3.types._fbthrift_map_private_ctor,
        )
{{/type:is_container_of_struct?}}
{{/program:inplace_migrate?}}

Mapping.register({{type:flat_name}})
{{/type:map?}}
